<!-- Created by mhy on 2018/7/1.

 -->
<template>
  <div ref="container" id="picture-input" class="picture-input">
    <div v-if="!supportsUpload" v-html="strings.upload"></div>
    <div class="change-it" v-else-if="supportsPreview">
      <div class="preview-container"
           :style="{maxWidth: previewWidth + 'px', height: previewHeight + 'px'}">
        <canvas ref="previewCanvas"
                class="picture-preview"
                :class="computedClasses"
                @drag.stop.prevent=""
                @dragover.stop.prevent=""
                @dragstart.stop.prevent="onDragStart"
                @dragenter.stop.prevent="onDragStart"
                @dragend.stop.prevent="onDragStop"
                @dragleave.stop.prevent="onDragStop"
                @drop.stop.prevent="onFileDrop"
                @click.prevent="selectImage"
                :style="{height: previewHeight + 'px'}">
        </canvas>
        <div v-if="!imageSelected"
             class="picture-inner"
             :style="{top: -previewHeight + 'px', marginBottom: -previewHeight + 'px' }">
          <span v-if="supportsDragAndDrop" class="picture-inner-text" v-html="strings.drag"></span>
          <span v-else class="picture-inner-text" v-html="strings.tap"></span>
        </div>
      </div>
      <button v-if="imageSelected" @click.prevent="selectImage" :class="buttonClass">{{ strings.change }}</button>
      <button v-if="imageSelected && removable" @click.prevent="removeImage" :class="removeButtonClass">{{ strings.remove }}</button>
      <button v-if="imageSelected && toggleAspectRatio && width !== height" @click.prevent="rotateImage" :class="aspectButtonClass">{{ strings.aspect }}</button>
    </div>
    <div class="change-it" v-else>
      <button v-if="!imageSelected" @click.prevent="selectImage" :class="buttonClass">{{ strings.select }}</button>
      <div v-else>
        <div v-html="strings.selected"></div>
        <button @click.prevent="selectImage" :class="buttonClass">{{ strings.change }}</button>
        <button v-if="removable" @click.prevent="removeImage" :class="removeButtonClass">{{ strings.remove }}</button>
      </div>
    </div>
    <input ref="fileInput" type="file" :name="name" :id="id" :accept="accept" @change="onFileChange">
  </div>
</template>

<script>
  export default {
    name: 'picture-input',
    props: {
      width: {
        type: [String, Number],
        default: Number.MAX_SAFE_INTEGER
      },
      height: {
        type: [String, Number],
        default: Number.MAX_SAFE_INTEGER
      },
      margin: {
        type: [String, Number],
        default: 0
      },
      accept: {
        type: String,
        default: 'image/*'
      },
      size: {
        type: [String, Number],
        default: Number.MAX_SAFE_INTEGER
      },
      name: {
        type: String,
        default: null
      },
      id: {
        type: [String, Number],
        default: null
      },
      buttonClass: {
        type: String,
        default: 'btn btn-primary button'
      },
      removeButtonClass: {
        type: String,
        default: 'btn btn-secondary button secondary'
      },
      aspectButtonClass: {
        type: String,
        default: 'btn btn-secondary button secondary'
      },
      prefill: {
        type: [String, File],
        default: ''
      },
      crop: {
        type: Boolean,
        default: true
      },
      removable: {
        type: Boolean,
        default: false
      },
      autoToggleAspectRatio: {
        type: Boolean,
        default: false
      },
      toggleAspectRatio: {
        type: Boolean,
        default: false
      },
      customStrings: {
        type: Object,
        default: () => {
          return {}
        }
      }
    },
    watch: {
      prefill () {
        if (this.prefill) {
          this.preloadImage(this.prefill)
        } else {
          this.removeImage()
        }
      }
    },
    data () {
      return {
        imageSelected: false,
        previewHeight: 0,
        previewWidth: 0,
        draggingOver: false,
        canvasWidth: 0,
        canvasHeight: 0,
        strings: {
          upload: '<p>Your device does not support file uploading.</p>',
          drag: 'Drag an image or <br>click here to select a file',
          tap: 'Tap here to select a photo <br>from your gallery',
          change: 'Change Photo',
          aspect: 'Landscape/Portrait',
          remove: 'Remove Photo',
          select: 'Select a Photo',
          selected: '<p>Photo successfully selected!</p>',
          fileSize: 'The file size exceeds the limit',
          fileType: 'This file type is not supported.'
        }
      }
    },
    mounted () {
      this.updateStrings()
      if (this.prefill) {
        this.preloadImage(this.prefill)
      }

      this.$nextTick(() => {
        window.addEventListener('resize', this.onResize)
        this.onResize()
      })
      if (this.supportsPreview) {
        this.pixelRatio = Math.round(window.devicePixelRatio || window.screen.deviceXDPI / window.screen.logicalXDPI)
        const canvas = this.$refs.previewCanvas
        if (canvas.getContext) {
          this.context = canvas.getContext('2d')
          this.context.scale(this.pixelRatio, this.pixelRatio)
        }
      }
      if (this.accept !== 'image/*') {
        this.fileTypes = this.accept.split(',')
        this.fileTypes = this.fileTypes.map(s => s.trim())
      }

      this.canvasWidth = this.width
      this.canvasHeight = this.height
    },
    beforeDestroy () {
      window.removeEventListener('resize', this.onResize)
    },
    methods: {
      updateStrings () {
        for (let s in this.customStrings) {
          if (s in this.strings && typeof this.customStrings[s] === 'string') {
            this.strings[s] = this.customStrings[s]
          }
        }
      },
      onResize () {
        this.resizeCanvas()

        if (this.imageObject) {
          this.drawImage(this.imageObject)
        }
      },
      onDragStart () {
        if (!this.supportsDragAndDrop) {
          return
        }
        this.draggingOver = true
      },
      onDragStop () {
        if (!this.supportsDragAndDrop) {
          return
        }
        this.draggingOver = false
      },
      onFileDrop (e) {
        this.onDragStop()
        this.onFileChange(e)
      },
      onFileChange (e) {
        let files = e.target.files || e.dataTransfer.files
        if (!files.length) {
          return
        }
        if (files[0].size <= 0 || files[0].size > this.size * 1024 * 1024) {
          alert(this.strings.fileSize + ' (' + this.size + 'MB)')
          return
        }
        if (files[0].name === this.fileName && files[0].size === this.fileSize && this.fileModified === files[0].lastModified) {
          return
        }

        this.file = files[0]
        this.fileName = files[0].name
        this.fileSize = files[0].size
        this.fileModified = files[0].lastModified
        this.fileType = files[0].type
        if (this.accept === 'image/*') {
          if (files[0].type.substr(0, 6) !== 'image/') {
            return
          }
        } else {
          if (this.fileTypes.indexOf(files[0].type) === -1) {
            alert(this.strings.fileType)
            return
          }
        }
        this.imageSelected = true
        this.image = ''
        if (this.supportsPreview) {
          this.loadImage(files[0])
        } else {
          this.$emit('change')
        }
      },
      loadImage (file) {
        let self = this
        this.getEXIFOrientation(file, orientation => {
          this.setOrientation(orientation)
          let reader = new FileReader()
          reader.onload = e => {
            this.image = e.target.result
            // console.log('change', self.rotate)
            this.$emit('change', self.rotate)
            this.imageObject = new Image()
            this.imageObject.onload = () => {
              if (this.autoToggleAspectRatio) {
                let canvasOrientation = this.getOrientation(this.canvasWidth, this.canvasHeight)
                let imageOrientation = this.getOrientation(this.imageObject.width, this.imageObject.height)

                if (canvasOrientation !== imageOrientation) {
                  this.rotateCanvas()
                }
              }

              this.drawImage(this.imageObject)
            }
            this.imageObject.src = this.image
          }
          reader.readAsDataURL(file)
        })
      },
      drawImage (image) {
        this.imageWidth = image.width
        this.imageHeight = image.height
        this.imageRatio = image.width / image.height
        let offsetX = 0
        let offsetY = 0
        let scaledWidth = this.previewWidth
        let scaledHeight = this.previewHeight
        if (this.crop) {
          if (this.imageRatio > 1) {
            scaledWidth = scaledHeight * this.imageRatio
            offsetX = (this.previewWidth - scaledWidth) / 2
          } else {
            scaledHeight = scaledWidth / this.imageRatio
            offsetY = (this.previewHeight - scaledHeight) / 2
          }
        } else {
          if (this.imageRatio > 1) {
            scaledHeight = scaledWidth / this.imageRatio
            offsetY = (this.previewHeight - scaledHeight) / 2
          } else {
            scaledWidth = scaledHeight * this.imageRatio
            offsetX = (this.previewWidth - scaledWidth) / 2
          }
        }
        const canvas = this.$refs.previewCanvas
        canvas.style.background = 'none'
        canvas.width = this.previewWidth * this.pixelRatio
        canvas.height = this.previewHeight * this.pixelRatio
        this.context.setTransform(1, 0, 0, 1, 0, 0)
        this.context.clearRect(0, 0, canvas.width, canvas.height)
        if (this.rotate) {
          this.context.translate(offsetX * this.pixelRatio, offsetY * this.pixelRatio)
          this.context.translate(scaledWidth / 2 * this.pixelRatio, scaledHeight / 2 * this.pixelRatio)
          this.context.rotate(this.rotate)
          offsetX = -scaledWidth / 2
          offsetY = -scaledHeight / 2
        }
        this.context.drawImage(image,
          offsetX * this.pixelRatio,
          offsetY * this.pixelRatio,
          scaledWidth * this.pixelRatio,
          scaledHeight * this.pixelRatio)
      },
      selectImage () {
        this.$refs.fileInput.click()
      },
      removeImage () {
        this.$refs.fileInput.value = ''
        this.$refs.fileInput.type = ''
        this.$refs.fileInput.type = 'file'
        this.fileName = ''
        this.fileType = ''
        this.fileSize = 0
        this.fileModified = 0
        this.imageSelected = false
        this.image = ''
        this.file = null
        this.imageObject = null
        this.$refs.previewCanvas.style.backgroundColor = 'rgba(200,200,200,.25)'
        this.$refs.previewCanvas.width = this.previewWidth * this.pixelRatio
        this.$emit('remove')
      },
      rotateImage () {
        this.rotateCanvas()

        if (this.imageObject) {
          this.drawImage(this.imageObject)
        }

        let newOrientation = this.getOrientation(this.canvasWidth, this.canvasHeight)
        this.$emit('aspectratiochange', newOrientation)
      },
      resizeCanvas () {
        let previewRatio = this.canvasWidth / this.canvasHeight
        let newWidth = this.$refs.container.clientWidth
        if (!this.toggleAspectRatio && newWidth === this.containerWidth) {
          return
        }
        this.containerWidth = newWidth
        this.previewWidth = Math.min(this.containerWidth - this.margin * 2, this.canvasWidth)
        this.previewHeight = this.previewWidth / previewRatio
      },
      getOrientation (width, height) {
        let orientation = 'square'

        if (width > height) {
          orientation = 'landscape'
        } else if (width < height) {
          orientation = 'portrait'
        }

        return orientation
      },
      switchCanvasOrientation () {
        const canvasWidth = this.canvasWidth
        const canvasHeight = this.canvasHeight

        this.canvasWidth = canvasHeight
        this.canvasHeight = canvasWidth
      },
      rotateCanvas () {
        this.switchCanvasOrientation()
        this.resizeCanvas()
      },
      setOrientation (orientation) {
        this.rotate = false
        if (orientation === 8) {
          this.rotate = -Math.PI / 2
        } else if (orientation === 6) {
          this.rotate = Math.PI / 2
        } else if (orientation === 3) {
          this.rotate = -Math.PI
        }
        // console.log('this.rotate', this.rotate)
      },
      getEXIFOrientation (file, callback) {
        var reader = new FileReader()
        reader.onload = e => {
          var view = new DataView(e.target.result)
          if (view.getUint16(0, false) !== 0xFFD8) {
            return callback(-2)
          }
          var length = view.byteLength
          var offset = 2
          while (offset < length) {
            var marker = view.getUint16(offset, false)
            offset += 2
            if (marker === 0xFFE1) {
              if (view.getUint32(offset += 2, false) !== 0x45786966) {
                return callback(-1)
              }
              var little = view.getUint16(offset += 6, false) === 0x4949
              offset += view.getUint32(offset + 4, little)
              var tags = view.getUint16(offset, little)
              offset += 2
              for (var i = 0; i < tags; i++) {
                if (view.getUint16(offset + (i * 12), little) === 0x0112) {
                  return callback(view.getUint16(offset + (i * 12) + 8, little))
                }
              }
            } else if ((marker & 0xFF00) !== 0xFF00) {
              break
            } else {
              offset += view.getUint16(offset, false)
            }
          }
          return callback(-1)
        }
        reader.readAsArrayBuffer(file.slice(0, 65536))
      },
      preloadImage (source) {
        if (typeof source === 'object') {
          this.imageSelected = true
          this.image = ''
          if (this.supportsPreview) {
            this.loadImage(source)
          } else {
            this.$emit('change')
          }
          return
        }
        let headers = new Headers()
        headers.append('Accept', 'image/*')
        fetch(source, {
          method: 'GET',
          mode: 'same-origin',
          headers: headers
        }).then(response => {
          return response.blob()
        })
          .then(imageBlob => {
            let e = { target: { files: [] } }
            const fileName = source.split('/').slice(-1)[0]
            let fileType = fileName.split('.').slice(-1)[0]
            fileType = fileType.replace('jpg', 'jpeg')
            e.target.files[0] = new File([imageBlob], fileName, { type: 'image/' + fileType })
            this.onFileChange(e)
          })
          .catch(err => {
            console.log('Failed loading prefill image: ' + err.message)
          })
      }
    },
    computed: {
      supportsUpload () {
        if (navigator.userAgent.match(/(Android (1.0|1.1|1.5|1.6|2.0|2.1))|(Windows Phone (OS 7|8.0))|(XBLWP)|(ZuneWP)|(w(eb)?OSBrowser)|(webOS)|(Kindle\/(1.0|2.0|2.5|3.0))/)) {
          return false
        }
        const el = document.createElement('input')
        el.type = 'file'
        return !el.disabled
      },
      supportsPreview () {
        return window.FileReader && !!window.CanvasRenderingContext2D
      },
      supportsDragAndDrop () {
        const div = document.createElement('div')
        return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && !('ontouchstart' in window || navigator.msMaxTouchPoints)
      },
      computedClasses () {
        const classObject = {}
        classObject['dragging-over'] = this.draggingOver
        return classObject
      }
    }
  }
</script>

<style scoped>
  .picture-input {
  //position: relative;
  }
  .preview-container {
    width: 100%;
    box-sizing: border-box;
    margin: 0 auto;
    cursor: pointer;
    overflow: hidden;
  }
  .picture-preview {
    width: 100%;
    height: 100%;
    position: relative;
    z-index: 1;
    box-sizing: border-box;
    background-color: rgba(200,200,200,.25);
  }
  .picture-preview.dragging-over {
    filter: brightness(0.5);
  }
  .picture-inner {
    position: relative;
    z-index: 2;
    pointer-events: none;
    box-sizing: border-box;
    margin: 1em auto;
    padding: 3em;
    border: .3em dashed rgba(66,66,66,.15);
    border-radius: 8px;
    width: calc(100% - 2.5em);
    height: calc(100% - 2.5em);
    display: table;
  }
  .picture-inner .picture-inner-text {
    display: table-cell;
    vertical-align: middle;
    text-align: center;
    font-size: 2em;
    line-height: 1.5;
  }
  button {
    margin: 1em .25em;
    cursor: pointer;
  }
  input[type=file] {
    display: block;
    position: absolute;
    width: 100%;
    height: 100%;
    opacity: 0;
  }
  @media (max-width: 767px) {
    .picture-inner {
      padding: 2vw;
    }
    .picture-inner .picture-inner-text {
      font-size: 5vw;
    }
  }
  .change-it {
    display: none
  }

</style>
